package com.ouyunc.common.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.ouyunc.common.base.Request;
import com.ouyunc.common.constant.CommonConstant;
import com.ouyunc.common.context.ThreadLocalContextHolder;
import org.apache.commons.collections4.MapUtils;
import org.apache.http.Consts;
import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.*;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.entity.mime.content.InputStreamBody;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.util.UriComponentsBuilder;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Map;

/**
 * @Author fzx
 * @Description  httpclient工具类,
 * 注意，注意，注意：在调用相应方法的时候注意httpEntity 的资源释放，只需要使用EntityUtils.toXXX() 方法就可以自动释放资源，否则就会造成资源流集未关闭访问失败
 * @ClassName HttpClientUtil
 * @Date 2019/4/25 15:35
 */
public enum ApacheHttpClientUtil {

    INSTANCE;

    private static final Logger logger = LoggerFactory.getLogger(ApacheHttpClientUtil.class);

    /**
     * utf-8 编码
     **/
    private static final String UTF_8 = "UTF-8";
    // 连接池管理
    private static PoolingHttpClientConnectionManager cm;

    // 默认建立连接的超时10s
    private static final int DEFAULT_CONNECT_TIMEOUT = 10000;

    // 默认获取数据的超时时间10s
    private static final int DEFAULT_READ_TIMEOUT = 10000;


    // 初始化httpclient连接池
    private static void init() {
        if (cm == null) {
            cm = new PoolingHttpClientConnectionManager();
            // 整个连接池最大连接数
            cm.setMaxTotal(500);
            // 每路由最大连接数，默认值是2
            cm.setDefaultMaxPerRoute(50);
        }
    }

    /**
     * @Author fangzhenxun
     * @Description 在单例模式下，解决多线程并发时的的httpClient使用不同的本地副本，互不影响
     * @Date 2020/6/18 13:31
     * @param
     * @return org.apache.commons.httpclient.HttpClient
     **/
    private static CloseableHttpClient getHttpClient(){
        try {
            // 初始化池信息
            init();
            // 跳过https 证书验证
            SSLContext sslContext = SSLContext.getInstance("TLS");
            X509TrustManager tm = new X509TrustManager() {
                @Override
                public void checkClientTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException {
                }
                @Override
                public void checkServerTrusted(java.security.cert.X509Certificate[] x509Certificates, String s) throws CertificateException {
                }
                @Override
                public X509Certificate[] getAcceptedIssuers() {
                    return new X509Certificate[]{};
                }
            };

            sslContext.init(null, new TrustManager[] { tm }, new SecureRandom());

            HostnameVerifier hostnameVerifier = (s, sslSession) -> true;

            SSLConnectionSocketFactory ssf = new SSLConnectionSocketFactory(
                    sslContext, hostnameVerifier);//NoopHostnameVerifier.INSTANCE
            return HttpClients.custom()
                    .setSSLSocketFactory(ssf)
                    .setSSLHostnameVerifier(hostnameVerifier)
                    .setConnectionManager(cm).build();
        } catch (Exception e) {
            logger.error("https client 初始化失败！开始http client 初始化...");
            // 使用http 请求
            return HttpClients.createDefault();
        }
    }



    public HttpEntity doGet(Request request){
        HttpGet httpGet = new HttpGet(request.getUrl());
        return  common(httpGet, request);
    }

    public HttpEntity doPost(Request request){
        HttpPost httpPost = new HttpPost(request.getUrl());
        return  common(httpPost, request);
    }

    public HttpEntity doPut(Request request){
        HttpPut httpPut = new HttpPut(request.getUrl());
        return  common(httpPut, request);
    }

    public HttpEntity doDelete(Request request){
        HttpDelete httpDelete = new HttpDelete(request.getUrl());
        return  common(httpDelete, request);
    }

    /**
     * post 请求  上传
     * @param request
     * @return
     */
    public HttpEntity doUpload(Request request){
        HttpPost httpPost = new HttpPost(request.getUrl());
        return common(httpPost, request);
    }

    /**
     * 公共方法
     * @date  2019/4/25 18:13
     */
    private HttpEntity common(HttpUriRequest method, Request request) {
        try {
            //设置请求配置，如超时时间
            ((HttpRequestBase) method).setConfig(RequestConfig.custom()
                    .setConnectTimeout(request.getConnectTimeout() <= 0 ? DEFAULT_CONNECT_TIMEOUT : request.getConnectTimeout() )
                    .setSocketTimeout(request.getReadTimeout() <= 0 ? DEFAULT_READ_TIMEOUT : request.getReadTimeout())
                    .build());
            // 设置默认请求头
            Map<String, String> currentRequestHeaders = ThreadLocalContextHolder.get(CommonConstant.CURRENT_REQUEST_HEADERS);
            if (MapUtils.isNotEmpty(currentRequestHeaders)) {
                // @TODO  这里根据需要可以从当前线程上线文中获取请求头或请求参数信息
                //setRequestHeader(method, currentRequestHeaders);
            }
            //如果自定义请求头不为空则设置请求头
            if (MapUtils.isNotEmpty(request.getHeader())) {
                setRequestHeader(method, request.getHeader());
            }
            //设置请求参数
            setRequestParams(method, request);
            //获取响应
            CloseableHttpResponse response = getHttpClient().execute(method);
            //注意HttpEntity 只能用一次取值，第二次就为空了
            return response.getEntity();
        } catch (Exception e) {
            logger.error("http client 发送请求失败： {}", e.getMessage());
            e.printStackTrace();
        }
        throw new RuntimeException("http client 发送请求失败");
    }


        /**
        * @author fzx
        * @description  设置请求头header
        * @param: method
        * @param: token
        * @return  void
        * @date  2019/4/25 16:38
        */
    private static void setRequestHeader(HttpUriRequest method, Map<String, String> requestHeader) {
        requestHeader.forEach((key,value) -> method.setHeader(key, value));
    }

    /**
    * @author fzx
    * @description  设置请求参数
    * @param: method
    * @param: requestParams
    * @return  void
    * @date  2019/4/25 17:32
    */
    private static void setRequestParams(HttpUriRequest method, Request request) throws Exception {

        try {
            // 判断是否是文件上传
            if (MapUtils.isNotEmpty(request.getFiles()) && method instanceof HttpPost) {
                UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(method.getURI());
                ((HttpPost) method).setURI(uriBuilder.encode().build().toUri());

                MultipartEntityBuilder builder = MultipartEntityBuilder.create();
                //设置浏览器兼容模式
                builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
                //设置请求的编码格式
                builder.setCharset(Consts.UTF_8);
                builder.setContentType(ContentType.MULTIPART_FORM_DATA);
                //添加其他RequestParam 参数
                if (MapUtils.isNotEmpty(request.getParam())) {
                    request.getParam().forEach((paramKey, paramValue)->{
                        builder.addTextBody(paramKey, paramValue);
                    });
                }
                //添加文件
                for (Map.Entry<String, List<MultipartFile>> entry : request.getFiles().entrySet()) {
                    // 应该只有一个key
                    for (MultipartFile file : entry.getValue()) {
                        builder.addPart(entry.getKey(), new InputStreamBody(file.getInputStream(), ContentType.APPLICATION_OCTET_STREAM, file.getOriginalFilename()));
                    }
                }
                HttpEntity reqEntity = builder.build();
                ((HttpPost) method).setEntity(reqEntity);
                return;
            }

            //get 方法设置参数,防止路径中文乱码
            if (method instanceof HttpGet) {
                UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(method.getURI());
                if (MapUtils.isNotEmpty(request.getParam())) {
                    request.getParam().forEach((key,value) -> uriBuilder.queryParam(key, value));
                }
                ((HttpGet) method).setURI(uriBuilder.encode().build().toUri());
                return;
            }
            // post | put | delete 方法设置参数
            //参数不为空
            if (MapUtils.isNotEmpty(request.getParam())) {
                UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUri(method.getURI());
                request.getParam().forEach((key, value) ->{
                    uriBuilder.queryParam(key, value);
                });
                //直接拼在url路径上，不然如果同时存在路径和body，会被覆盖
                if (method instanceof HttpPost) {
                    ((HttpPost) method).setURI(uriBuilder.encode().build().toUri());
                }
                if (method instanceof HttpPut) {
                    ((HttpPut) method).setURI(uriBuilder.encode().build().toUri());
                }
                if (method instanceof HttpDelete) {
                    ((HttpDelete) method).setURI(uriBuilder.encode().build().toUri());
                }
                if (method instanceof HttpPatch) {
                    ((HttpDelete) method).setURI(uriBuilder.encode().build().toUri());
                }
            }
            if (request.getBody() != null) {
                //RequestBody 上 form表单
                if (method instanceof HttpPost) {
                    ((HttpPost) method).setEntity(new StringEntity(JSON.toJSONString(request.getBody(), SerializerFeature.WriteMapNullValue), ContentType.APPLICATION_JSON));
                }
                if (method instanceof HttpPut) {
                    ((HttpPut) method).setEntity(new StringEntity(JSON.toJSONString(request.getBody(), SerializerFeature.WriteMapNullValue), ContentType.APPLICATION_JSON));
                }
                if (method instanceof HttpPatch) {
                    ((HttpPatch) method).setEntity(new StringEntity(JSON.toJSONString(request.getBody(), SerializerFeature.WriteMapNullValue), ContentType.APPLICATION_JSON));
                }
            }
        }catch (Exception e) {
            logger.error("http client 组装参数失败！");
            e.printStackTrace();
        }

    }
}
